feat: Competitor-Aware Content Differentiation (#37)#54
Open
Conversation
added 15 commits
March 16, 2026 06:16
…uilder" This reverts commit 42bc034.
…in QualityDimensionResultTest
- DifferentiationResult value object (similarityScore, differentiationScore, angles[], gaps[], recommendations[]) - DifferentiationAnalysisService: analyze() + enrichBrief() - Calculates similarity/differentiation scores via SimilarityCalculator - LLM-powered angle/gap/recommendation generation via LLMManager - Stores results in differentiation_analyses table - Brief enrichment injects competitor context into brief requirements - AnalyzeContentDifferentiationJob on 'competitor' queue - Unit tests: DifferentiationResultTest, DifferentiationAnalysisServiceTest, AnalyzeContentDifferentiationJobTest
- Add CompetitorAnalysisStage implementing PipelineStageContract
- Stage type: 'competitor_analysis'
- Runs after brief creation, before content generation
- Calls DifferentiationAnalysisService::enrichBrief() to add
competitor context (angles, gaps, recommendations) to brief metadata
- Configurable per-stage: enabled, similarity_threshold, max_competitors
- Skips gracefully when disabled or no similar competitors found
- Updates PipelineRun context with competitor_analysis data
- Update config/numen.php with 'competitor_analysis' section:
- enabled (COMPETITOR_ANALYSIS_ENABLED)
- similarity_threshold (COMPETITOR_SIMILARITY_THRESHOLD)
- max_competitors_to_analyze (COMPETITOR_MAX_ANALYZE)
- auto_enrich_briefs (COMPETITOR_AUTO_ENRICH_BRIEFS)
- Register CompetitorAnalysisStage in AppServiceProvider via
HookRegistry::registerPipelineStageClass() — works with existing
PipelineExecutor + PluginStageJob infrastructure
- Integration tests (tests/Feature/Competitor/CompetitorAnalysisStageTest.php):
- Stage type/label/schema contract
- Skips when stage config disabled
- Skips when global config disabled
- Skips gracefully with no competitors in DB
- Enriches brief when similar competitors exist
- Updates run context with competitor data
- Enriches brief requirements array
- Stage registered in HookRegistry
…ob, email/Slack/webhook channels (chunk 6/10)
…r, competitor nodes + similarity edges (chunk 7/10)
…rceManager, DifferentiationScoreWidget, trend chart (chunk 9/10)
…NGELOG, blog post (chunk 10/10)
- Add space_id (string 26, indexed) to migration - Add space_id to CompetitorContentItem $fillable and space() BelongsTo relation - Update CompetitorContentItemFactory to include space_id via Space::factory() - Fix ContentFingerprintFactory to use keyword => score format (not plain array) - Fix ContentFingerprintService::fingerprint() to handle ContentBrief, using target_keywords as primary topics for accurate similarity matching - Fix SimilarityCalculator::buildKeywordVector() to handle both numeric-indexed and associative keyword arrays - Add RefreshDatabase to CrawlerServiceTest (needed for DB-backed dedup test) - Add tests/bootstrap.php to fix APP_BASE_PATH for git worktree + symlinked vendor - Set APP_BASE_PATH in phpunit.xml so Application::inferBasePath() resolves correctly (vendor/ is symlinked to main repo; without APP_BASE_PATH, migrations load from wrong directory)
- Add space_id column to migration (string 26, ULID-compatible) - Add space_id to CompetitorContentItem fillable + space() BelongsTo - Fix ContentFingerprintService: handle ContentBrief model, use firstOrCreate to preserve seeded fingerprints in tests - Fix SimilarityCalculator: handle indexed keyword arrays (list format) alongside associative (term => score) format - Add APP_BASE_PATH to phpunit.xml so RefreshDatabase finds correct migrations in the git worktree
…r differentiation - IDOR: Add space ownership checks to CompetitorController (crawl, alerts, destroyAlert), CompetitorSourceController (index, store, show, update, destroy), DifferentiationController (index, show, summary), and GraphQL mutations (TriggerCompetitorCrawl, DeleteCompetitorSource, DeleteCompetitorAlert, UpdateCompetitorSource) - SSRF: Add ExternalUrl rule to url/feed_url in StoreCompetitorSourceRequest and UpdateCompetitorSourceRequest; add ExternalUrl rule to slack_webhook/webhook_url in StoreCompetitorAlertRequest - Space scoping: Verify space_id access on all collection endpoints - Rate limiting: Add throttle:5,1 middleware to crawl trigger route - Quota: Enforce max 50 competitor sources per space in store() - Pre-existing: Remove duplicate match arm and duplicate extractFromBrief() method in ContentFingerprintService (caused phpstan errors)
0eed4fc to
de44e9a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Multi-source Content Differentiation
Implements comprehensive competitor analysis and content differentiation:
Infrastructure
Crawler Infrastructure
CrawlerServiceorchestrates RSS, sitemap, scrape, and API crawlersCrawlCompetitorSourceJobwith automatic retries and stale-checkFingerprinting & Similarity Analysis
ContentFingerprintService— TF-IDF vectorization of contentSimilarityCalculator— cosine similarity between fingerprintsSimilarContentFinder— identifies top-N similar competitor itemsDifferentiation Analysis Engine
DifferentiationResultvalue object for structured outputCompetitorAnalysisStagepipeline integration for automatic enrichmentAlert System
CompetitorAlertServicewith configurable rulesKnowledge Graph Integration
competitor_similarityedges for relationship trackingREST API
CompetitorSourceController— full CRUD for sourcesCompetitorController— content listing, crawl trigger, alertsDifferentiationController— analysis browsing and summarySecurity Hardening
manage-competitorsrole requiredMonitoring & Retention
CrawlerHealthMonitordetects stale/high-error sourcesRetentionPolicyServiceprunes old data on scheduleConfiguration
New environment variables:
Closes #37